#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iostream>

#define MPI_FILEPARSER

#ifndef MPI_H
#include <mpi.h>
#endif

#ifndef MPI_HELPER
#include "mpi_helper.h"
#endif

#ifndef DCS_ITEM
struct DCS_item {
	int i;
	float w;
};
#define DCS_ITEM
#endif


class FileParserBase {

public:
	int np, rank, count;

	FileParserBase() {
		MPI_Comm_rank(MPI_COMM_WORLD, &rank);
		MPI_Comm_size(MPI_COMM_WORLD, &np);
		fprintf(stderr, "%s\n", "get rank and np");
	}
	void init();
	template<class T>
	bool getinput(T * entries);

	int getcount() {
		return count;
	}
};

class MPIFileParser: public FileParserBase {
public:
	MPI_File * infile;
	MPI_File fh;
	MPI_Status status;
	MPI_Offset offset;
	MPI_Datatype entrytype;
	int curr, rc;
	//, np, rank, count;

	MPIFileParser(char* filename) {
		fprintf(stderr, "%s\n", "Called MPIFIle constructor");
		int rc = MPI_File_open(MPI_COMM_WORLD, filename, MPI_MODE_RDONLY,
				MPI_INFO_NULL, &fh);
		if (rc) {
			fprintf(stderr, "Unable to open file\n");
			fflush (stdout);
		}
		fprintf(stderr, "%s\n", "Opened MPIFIle");
	}
	;

	void closefile() {
		MPI_File_close(&this->fh);
	}

	MPIFileParser(MPI_File *file) :
			FileParserBase(), infile(file) {
	}
};

class ISFileFileParser: public FileParserBase {
public:
	ifstream *infile;
	vector<string> tokens;

	ISFileFileParser(ifstream *file) :
			FileParserBase(), infile(file) {

	}
	template<class T>
	bool getinput(T * entries, vector<T> * entries_vec);
};

template<class T>
class FileParser {
public:

};

template<>
class FileParser<COO_item> : public MPIFileParser {
private:
	int nzr;
	int *row_counts, *row_offsets;

	void init() {
		MPI_File_read_at(*infile, 0, &nzr, 1, MPI_INT, &status);
		row_counts = (int*) malloc(nzr * sizeof(int));
		MPI_File_read(*infile, row_counts, nzr, MPI_INT, &status);

		row_offsets = (int*) malloc(nzr * sizeof(int));
		row_offsets[0] = 0;
		for (int i = 1; i < nzr; i++) {
			row_offsets[i] = row_offsets[i - 1] + row_counts[i - 1];
		}
		MPI_File_get_position(*infile, &offset);
		COO_ITEM_TYPE (&entrytype);
		this->curr = (int) (nzr / np * this->rank) % nzr;
	}
public:
	FileParser<COO_item>(MPI_File *file) :
			MPIFileParser(file) {
		init();
	}
	;
	~FileParser<COO_item>() {
		delete row_counts;
		delete row_offsets;
	}
	bool getinput(COO_item* entries) {
		if (row_offsets[curr] != -1) {
			entries = (COO_item*) realloc(entries,
					sizeof(COO_item) * row_counts[curr]);
			MPI_File_seek(*infile,
					offset + (row_offsets[curr] * sizeof(COO_item)),
					MPI_SEEK_SET);
			MPI_File_read(*infile, entries, row_counts[curr], entrytype,
					&status);
			count = row_counts[curr];
			row_counts[curr] = -1;
			return true;
		} else {
			return false;
		}
	}
};

template<>
class FileParser<DCS_item> : public MPIFileParser {
private:
	int * header;
	int * JC_CP;
	int JC_SIZE, CP_SIZE;

	void init() {
		fprintf(stderr, "%s\n", "Calling init()");
		header = (int*) malloc(4 * sizeof(int));
		MPI_File_read(*infile, header, 4, MPI_INT, &this->status);
		JC_SIZE = header[0];
		CP_SIZE = header[1];
		JC_CP = (int*) calloc(JC_SIZE + CP_SIZE, sizeof(int));
		MPI_File_read(*infile, JC_CP, JC_SIZE + CP_SIZE, MPI_INT, &status);
		MPI_File_get_position(*infile, &offset);
		MPI_Free_mem(header);
		DCS_ENTRYTYPE (&entrytype);
		this->curr = (int) (JC_SIZE / np * this->rank) % JC_SIZE;
		fprintf(stderr, "%s\n", "Completed  init()");
	}

public:
	FileParser<DCS_item>(MPI_File *file) :
			MPIFileParser(file) {
		fprintf(stderr, "%s\n", "Calling fileparser constructor");
		init();
	}

	~FileParser<DCS_item>() {
		delete JC_CP;
		MPI_Type_free (&entrytype);
	}

	bool getinput(DCS_item * entries) {
		fprintf(stderr, "%s\n", "calling getinput");
		if (JC_CP[curr] != -1) {
			int ir_start = JC_CP[JC_SIZE + curr];
			int ir_end = JC_CP[JC_SIZE + curr + 1];
			int buflen = ir_end - ir_start;
			MPI_File_seek(*infile,
					offset + (ir_start * (sizeof(int) + sizeof(float))),
					MPI_SEEK_SET);
			entries = (DCS_item*) realloc(entries, sizeof(DCS_item) * buflen);
			MPI_File_read(*infile, entries, buflen, entrytype, &status);
			JC_CP[curr] = -1;
			curr = (curr + 1) % JC_SIZE;
			fprintf(stderr, "%s\n", "completed getinput");
			count = buflen;
			return true;

		} else {
			return false;
		}
	}

};

template<>
class FileParser<float> : public ISFileFileParser {

public:
	FileParser<float>(ifstream *file) :
			ISFileFileParser(file) {
		fprintf(stderr, "%s\n", "called TxtFileParser constructor");

		if (!file->is_open())
			perror("Error opening file");
	}

	bool getinput(float * float_arr, vector<float> * float_vec) {
		if (!this->infile->eof()) {
			string line = "";
			do {
				getline(*infile, line);
				trim(line);
				if (infile->eof())
					return false;
			} while (line.size() == 0);

			boost::split(tokens, line, boost::is_any_of(" "));
			float_arr = (float*) realloc(float_arr,
					sizeof(float) * tokens.size());
			for (int i = 0; i < tokens.size(); i++) {
				float_arr[i] = lexical_cast<float>(tokens[i]);
				float_vec->push_back(float_arr[i]);
			}
			count = float_vec->size();
			tokens.clear();
			return true;
		} else {
			return false;
		}
	}
	int getcount() {
		return count;
	}
};

/*class FileParserFactory {
public:

	static MMapFileParser<DCS> * getMMapFileParser(string format) {

		return NULL;
	}
};*/


class MMapFileParser {
private:
	float *num_offset;
	int *ir_offset, curr;
public:
	int *JC, *CP, *CURR_PTR, fd, NNZ_SIZE, IR_SIZE, count, *COUNTS;
	float *NUM_PTR;
	struct stat fs;
	void * map;

	void init() {
		int CP_SIZE;
		this->ir_offset = (int *) this->map;

		this->NNZ_SIZE = this->ir_offset[0];
		CP_SIZE = this->ir_offset[1];
		IR_SIZE = this->ir_offset[2];

		this->JC = (int*) malloc(this->NNZ_SIZE * sizeof(int));
		this->CP = (int*) malloc(CP_SIZE * sizeof(int));

		memcpy(this->JC, (this->ir_offset + 4), NNZ_SIZE * sizeof(int));
		memcpy(this->CP, (this->ir_offset + 4 + NNZ_SIZE),
				CP_SIZE * sizeof(int));
		COUNTS = (int*) malloc(sizeof(int) * (NNZ_SIZE + 1));
		int max = -1;
		for (int i = 0; i < NNZ_SIZE; i++) {
			COUNTS[i] = CP[i + 1] - CP[i];
			//fprintf(stderr,"%d ", COUNTS[i]);
			if (COUNTS[i] > max)
				max = COUNTS[i];
		}
		COUNTS[NNZ_SIZE] = max;

		/*fprintf(stderr,"\n", "");
		 for (int j = 0; j < NNZ_SIZE; j++) {
		 fprintf(stderr,"%d ", JC[j]);
		 }
		 fprintf(stderr,"\n", "");
		 for (int j = 0; j < CP_SIZE; j++) {
		 fprintf(stderr,"%d ", CP[j]);
		 }
		 fprintf(stderr,"\n", "");
		 for (int j = 0; j < NNZ_SIZE+1; j++) {
		 fprintf(stderr,"%d ", COUNTS[j]);
		 }
		 fprintf(stderr,"\n", "");*/

		ir_offset += 4 + NNZ_SIZE + CP_SIZE;
		num_offset = (float*) (ir_offset + IR_SIZE);

		//fprintf(stderr,"finished mmap init\n", "");
		curr = 0;
	}

	MMapFileParser(char * filename) {
		this->fd = open(filename, O_RDONLY);

		if (fd == -1) {
			perror("Error opening file for reading");
			exit (EXIT_FAILURE);
		}

		if (fstat(fd, &fs) == -1) {
			perror("Error on fstat");
			exit (EXIT_FAILURE);
		}
		//fprintf(stderr,"Opening file: %d kb\n", (int) fs.st_size);
		map = mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0);

		if (map == MAP_FAILED) {
			close(fd);
			perror("Error mmapping the file");
			exit (EXIT_FAILURE);
		}
		init();
	}

	bool getinput() {
		if (JC[curr] != -1) {
			//fprintf(stderr,"getting input\n");
			int ir_begin = CP[curr];
			int ir_end = CP[curr + 1];
			count = ir_end - ir_begin;
			CURR_PTR = (ir_offset + ir_begin);
			NUM_PTR = (float*) (num_offset + ir_begin);
			JC[curr] = -1;
			curr = (curr + 1) % NNZ_SIZE;
			return true;
		} else {
			if (munmap(map, fs.st_size) == -1) {
				perror("Error un-mmapping the file");
			}
			close(this->fd);
			return false;
		}
	}
};

